﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Xceed.Words.NET;

namespace FiveMonth.FormWork.Word
{
    /// <summary>
    /// Word模板
    /// </summary>
    public static class Template
    {
        class DocInfo
        {
            internal List<Bookmark> Bookmarks { get; }

            internal List<LoopInfo> Loops { get; }

            internal DocX Doc { get; set; }

            public DocInfo()
            {
                this.Bookmarks = new List<Bookmark>();
                this.Loops = new List<LoopInfo>();
            }
        }

        private static Regex regex = new Regex("@@([^@]*)@@", RegexOptions.Compiled | RegexOptions.Multiline);

        private static DocInfo Compile(string tplfile)
        {
            if (!File.Exists(tplfile))
            {
                throw new Exception("模板文件不存在。");
            }
            try
            {
                var tplInfo = new DocInfo { Doc = DocX.Load(tplfile) };


                var allLoops = new List<LoopInfo>();

                #region 找到所有的标签

                var paras = GetElementParagraghs(tplInfo.Doc);
                var books = new Dictionary<string, Element>();

                foreach (var para in paras)
                {
                    var text = para.ParagraphText();
                    if (string.IsNullOrWhiteSpace(text))
                    {
                        continue;
                    }
                    var matches = regex.Matches(text);
                    if (matches.Count > 0)
                    {
                        for (int i = 0; i < matches.Count; i++)
                        {
                            var match = matches[i];
                            if (match.Success)
                            {
                                var name = match.Groups[1].Value;
                                books.Add(name, para);
                            }
                        }
                    }
                }

                #endregion

                #region 找到所有循环开始结束书签，和所有循环内部的书签

                foreach (var booksKey in books.Keys)
                {
                    var name = booksKey;
                    if (!name.Contains("_"))
                    {
                        tplInfo.Bookmarks.Add(new Bookmark() { Name = name, Paragraph = books[name].As<Paragraph>() });
                    }
                    //循环头和循环尾
                    else if (name.EndsWith("_"))
                    {
                        var lname = name.Replace("__", "_");

                        var loop = allLoops.FirstOrDefault(x => x.Name == lname);
                        if (loop == null)
                        {
                            loop = new LoopInfo() { Name = lname };
                            allLoops.Add(loop);
                        }
                        //循环头
                        if (name.EndsWith("__"))
                        {
                            loop.LastElement = books[name];
                        }
                        else
                        {
                            loop.FirstElement = books[name];
                        }
                    }
                }

                #endregion

                #region 循环内部书签

                foreach (var booksKey in books.Keys)
                {
                    var name = booksKey;
                    if (!name.EndsWith("_") && name.Contains("_"))
                    {
                        foreach (var loop in allLoops)
                        {
                            if (IsInLoop(name, loop.Name))
                            {
                                loop.Bookmarks.Add(new Bookmark() { Name = name });
                            }
                        }
                    }
                }

                #endregion

                tplInfo.Loops.AddRange(CreateTree(allLoops));
                return tplInfo;
            }
            catch (Exception e)
            {
                throw new Exception("模板编译失败，无法应用数据。", e);
            }
        }

        private static bool IsInLoop(string name, string loopName)
        {
            if (!name.StartsWith(loopName))
            {
                return false;
            }
            return !name.Replace(loopName, "").Contains("_");
        }

        private static List<LoopInfo> CreateTree(List<LoopInfo> infos)
        {
            if (infos == null || !infos.Any())
            {
                return infos ?? new List<LoopInfo>();
            }
            Func<string, int> level = s => s.Count(x => x.Equals('_'));
            var lvs = infos.Select(x => level(x.Name)).Distinct().OrderBy(x => x).ToList();

            List<LoopInfo> tops = null;
            var onllv = new List<LoopInfo>();
            foreach (var lv in lvs)
            {
                var lst = infos.Where(x => level(x.Name) == lv).ToList();
                if (tops == null)
                {
                    tops = lst;
                }
                else
                {
                    onllv.ForEach(x =>
                    {
                        x.Chidren = lst.Where(l => l.Name.StartsWith(x.Name)).ToList();
                        x.Chidren.ForEach(l => l.Parent = x);
                    });
                }

                onllv = lst;
            }

            return tops;
        }

        /// <summary>
        /// 对模板应用数据
        /// </summary>
        /// <param name="tplfile"></param>
        /// <param name="file">应用数据成功后保存文件的路径</param>
        /// <param name="data">对模板应用的数据</param>
        /// <param name="mappers">模板书签信息</param>
        /// <returns></returns>
        public static bool ApplyData<T>(string tplfile, string file, T data, BookmarkInfos<T> mappers)
        {
            DocInfo docInfo = Compile(tplfile);

            try
            {
                ApplyData(docInfo, data, mappers);
                docInfo.Doc.SaveAs(file);
                return true;
            }
            catch (Exception e)
            {
                throw new Exception("模板应用数据错误。", e);
            }
            finally
            {
                docInfo.Doc.Dispose();
            }
        }

        private static void ApplyData<T>(DocInfo docInfo, T data, BookmarkInfos<T> mappers)
        {
            if (mappers == null || !mappers.Any())
            {
                return;
            }

            var mapperDic = mappers.ToDictionary();
            foreach (var bookmark in docInfo.Bookmarks)
            {
                ApplyBookmark(docInfo, data, mapperDic, bookmark.Name, bookmark.Paragraph);
            }

            ApplyLoops(docInfo, data, mapperDic, docInfo.Loops);
        }

        private static void ApplyBookmark<T>(DocInfo docInfo, T data, Dictionary<string, BookmarkInfo<T>> mapperDic,
            string bookmarkName, DocXElement element, IList<int> argIndex = null)
        {
            if (!mapperDic.ContainsKey(bookmarkName) || mapperDic[bookmarkName] == null)
            {
                return;
            }

            var bvalue = mapperDic[bookmarkName];
            var value = bvalue?.ValueFunc(data, argIndex);
            if (value == null)
            {
                return;
            }

            if (argIndex == null)
            {
                if (bvalue.Type == BookmarkType.Image)
                {
                    ReplaceWithImage(docInfo.Doc, bookmarkName, element, value, bvalue.Width, bvalue.Height);
                }
                else if (bvalue.Type == BookmarkType.String)
                {
                    ReplaceWithValue(bookmarkName, element, value);
                }
            }
            else
            {
                if (bvalue.Type == BookmarkType.Image)
                {
                    ReplaceWithImage(docInfo.Doc, bookmarkName, element, value, bvalue.Width, bvalue.Height);
                }
                else if (bvalue.Type == BookmarkType.String)
                {
                    ReplaceWithValue(bookmarkName, element, value);
                }
            }
        }

        private static void ApplyLoops<T>(DocInfo docInfo, T data, Dictionary<string, BookmarkInfo<T>> mapperDic,
            List<LoopInfo> theLoops, List<int> arg = null, List<Element> parentRows = null)
        {
            foreach (var loop in theLoops)
            {
                var loopCount = 0;
                if (mapperDic.ContainsKey(loop.Name))
                {
                    var bloop = mapperDic[loop.Name];
                    loopCount = bloop?.LoopCountFunc(data, arg) ?? 0;
                }

                var rows = GetLoopRows(loop, parentRows);
                if (rows == null || !rows.Any())
                {
                    loopCount = 0;
                }

                for (int i = 0; i < loopCount; i++)
                {
                    var argList = arg?.ToList();
                    if (argList == null)
                    {
                        argList = new List<int> { i };
                    }
                    else
                    {
                        argList.Add(i);
                    }

                    var firstEle = loop.FirstElement;
                    if (firstEle.Parent.Real is Cell)
                    {
                        var firstRowEle = rows.First();
                        var tableEle = firstRowEle.Parent;
                        var table = tableEle.As<Table>();
                        var firstRowIndex = tableEle.Children.IndexOf(firstRowEle);
                        var prs = new List<Element>();
                        foreach (var row in rows)
                        {
                            var newItem = table.InsertRow(row.As<Row>(), firstRowIndex);
                            var newRowEle = new Element(newItem, tableEle);
                            tableEle.Children.Insert(firstRowIndex, newRowEle);
                            firstRowIndex++;
                            foreach (var bookmark in loop.Bookmarks)
                            {
                                ApplyBookmark(docInfo, data, mapperDic, bookmark.Name, newItem, argList);
                            }
                            newItem.ReplaceText("@@" + loop.Name + "@@", "");
                            newItem.ReplaceText("@@" + loop.Name + "_@@", "");
                            prs.Add(newRowEle);
                        }

                        if (loop.Chidren != null)
                        {
                            ApplyLoops(docInfo, data, mapperDic, loop.Chidren, argList, prs);
                        }
                    }
                    else
                    {
                        throw new Exception("不支持非表格的循环。");
                    }
                }

                foreach (var rowEle in rows)
                {
                    var row = rowEle.As<Row>();
                    row.Remove();
                    rowEle.Parent.Children.Remove(rowEle);
                }
            }
        }


        #region 文档操作方法
        private static void ReplaceWithValue(string bookmarkName, DocXElement element, string text)
        {
            var paragraph = element as Paragraph;
            if (paragraph != null)
            {
                paragraph.ReplaceText("@@" + bookmarkName + "@@", text);
                return;
            }
            var row = element as Row;
            if (row != null)
            {
                row.ReplaceText("@@" + bookmarkName + "@@", text);
                return;
            }
        }

        private static void ReplaceWithImage(DocX document, string bookmarkName, DocXElement element, string imageFile, int width, int height)
        {
            var paragraph = element as Paragraph;
            if (paragraph != null)
            {
                //插入图片  
                paragraph.ReplaceText("@@" + bookmarkName + "@@", "");
                var image = document.AddImage(imageFile);
                var picture = image.CreatePicture(width, height);
                paragraph.AppendPicture(picture);
                return;
            }
            var row = element as Row;
            if (row != null)
            {
                foreach (var cellp in row.Paragraphs)
                {
                    ReplaceWithImage(document, bookmarkName, cellp, imageFile, width, height);
                }
                return;
            }
        }

        #endregion

        #region 循环信息

        private static List<Element> GetLoopRows(LoopInfo loopInfo, List<Element> parentRows)
        {
            if (loopInfo.Parent != null)
            {
                var tableEle = loopInfo.FirstElement.Parent.Parent.Parent;
                var pindex = tableEle.Children.IndexOf(loopInfo.Parent.FirstElement.Parent.Parent);
                var thisIndex = tableEle.Children.IndexOf(loopInfo.FirstElement.Parent.Parent);
                var thisLastIndex = tableEle.Children.IndexOf(loopInfo.LastElement.Parent.Parent);
                var rowCount = thisLastIndex - thisIndex + 1;
                return parentRows.Skip(thisIndex - pindex).Take(rowCount).ToList();
            }

            var first = loopInfo.FirstElement;
            var last = loopInfo.LastElement;
            if (!(first.Parent.Real is Cell) || !(last.Parent.Real is Cell))
            {
                return null;
            }
            if (first.Parent.Parent.Parent.Real != last.Parent.Parent.Parent.Real)
            {
                return null;
            }

            var f = first.Parent.Parent;
            var l = last.Parent.Parent;
            var t = f.Parent;
            var rst = new List<Element>();

            var enter = false;
            foreach (var row in t.Children)
            {
                if (!enter && row == f)
                {
                    enter = true;
                }
                if (enter)
                {
                    rst.Add(row);
                }
                if (row == l)
                {
                    break;
                }
            }

            return rst;
        }

        private static List<Element> GetElementParagraghs(DocX doc)
        {
            var ele = new Element(doc, null) { Children = new List<Element>() };
            var rst = new List<Element>();
            foreach (var paragraph in doc.Paragraphs.Where(x => x.ParentContainer == ContainerType.Body))
            {
                var p = new Element(paragraph, ele);
                ele.Children.Add(p);
                rst.Add(p);
                var table = paragraph.FollowingTable;
                if (table != null)
                {
                    var t = new Element(table, ele) { Children = new List<Element>() };
                    ele.Children.Add(t);
                    rst.AddRange(GetElementParagraghs(table, t));
                }
            }

            return rst;
        }

        private static List<Element> GetElementParagraghs(Table table, Element t)
        {
            var rst = new List<Element>();
            foreach (var row in table.Rows)
            {
                var prow = new Element(row, t) { Children = new List<Element>() };
                t.Children.Add(prow);
                foreach (var cell in row.Cells)
                {
                    var pcell = new Element(cell, prow) { Children = new List<Element>() };
                    prow.Children.Add(pcell);
                    foreach (var paragraph in cell.Paragraphs)
                    {
                        var pp = new Element(paragraph, pcell);
                        pcell.Children.Add(pp);
                        rst.Add(pp);
                    }
                }
            }
            return rst;
        }

        #endregion
    }
}
